package japgolly.scalajs.react.extra.internal

import japgolly.scalajs.react.internal.ValueOfCompat.ValueOf
import japgolly.microlibs.compiletime.*
import japgolly.microlibs.compiletime.MacroEnv.*
import japgolly.scalajs.react.extra.router.StaticDsl.{Route, RouteB, RouteCommon}
import scala.compiletime.*
import scala.deriving.Mirror
import scala.language.`3.0`
import scala.quoted.*

// This is here in .internal because users are expected to import router._ and it's
// better if this isn't visible and imported with the rest of the package.
object RouterMacros {

  // ===================================================================================================================
  // TODO: https://github.com/lampepfl/dotty/issues/12509
  // trait ForRoute [A] extends Common[Route, A] { self: Route[A] => }
  // trait ForRouteB[A] extends Common[RouteB, A] { self: RouteB[A] => }

  // trait Common[R[x] <: RouteCommon[R,x], A] { self: RouteCommon[R, A] =>

  //   /** Maps the captures values of the route to a case class. */
  //   inline def caseClass[B <: Product](using m: Mirror.ProductOf[B]): R[B] =
  //     ${ caseCaseMacro[R, A, B]('m, 'self) }

  //   /** Same as [[caseClass]] except the code generated by the macro is printed to stdout. */
  //   inline def caseClassDebug[B <: scala.Product](using m: Mirror.ProductOf[B]): R[B] =
  //     InlineUtils.printCode(caseClass[B])
  // }

  // private def caseCaseMacro[R[x] <: RouteCommon[R,x], A, B <: Product]
  //                          (mirror: Expr[Mirror.Of[B]], self: Expr[RouteCommon[R, A]])
  //                          (using Quotes, Type[A], Type[B], Type[R]): Expr[R[B]] = {
  //   import quotes.reflect.*
  //   mirror match {
  //     blah blah blah
  //   }
  // }
  // ===================================================================================================================

  trait ForRoute[A] { self: Route[A] =>

    /** Maps the captures values of the route to a case class. */
    inline def caseClass[B <: Product](using m: Mirror.ProductOf[B]): Route[B] =
      ${ caseCaseMacro[A, B]('m, 'self) }

    /** Same as [[caseClass]] except the code generated by the macro is printed to stdout. */
    inline def caseClassDebug[B <: scala.Product](using m: Mirror.ProductOf[B]): Route[B] =
      InlineUtils.printCode(caseClass[B])
  }

  private def caseCaseMacro[A, B <: Product]
                            (mirror: Expr[Mirror.Of[B]], self: Expr[Route[A]])
                            (using Quotes, Type[A], Type[B]): Expr[Route[B]] = {
    import quotes.reflect.*
    mirror match {

      case '{ $m: Mirror.ProductOf[B] { type MirroredElemTypes = EmptyTuple }} =>
        val ev = Expr.summonOrError[ValueOf[A]]
        '{ $self.const[B]($m.fromProduct(EmptyTuple))($ev) }

      case '{ $m: Mirror.ProductOf[B] { type MirroredElemTypes = t *: EmptyTuple }} =>
        Expr.summonOrError[A =:= t]
        '{
          $self.xmap[B](
            a => $m.fromProduct(a.asInstanceOf[t] *: EmptyTuple))(
            b => Tuple.fromProduct(b).asInstanceOf[A *: EmptyTuple].head)
        }

      case '{ type t <: Product; $m: Mirror.ProductOf[B] { type MirroredElemTypes = `t` }} =>
        Expr.summonOrError[A =:= t]
        '{
          $self.xmap[B](
            a => $m.fromProduct(a.asInstanceOf[t]))(
            b => Tuple.fromProduct(b).asInstanceOf[A])
        }
    }
  }

  trait ForRouteB[A] { self: RouteB[A] =>

    /** Maps the captures values of the route to a case class. */
    inline def caseClass[B <: Product](using m: Mirror.ProductOf[B]): RouteB[B] =
      ${ caseCaseMacroB[A, B]('m, 'self) }

    /** Same as [[caseClass]] except the code generated by the macro is printed to stdout. */
    inline def caseClassDebug[B <: scala.Product](using m: Mirror.ProductOf[B]): RouteB[B] =
      InlineUtils.printCode(caseClass[B])
  }

  private def caseCaseMacroB[A, B <: Product]
                            (mirror: Expr[Mirror.Of[B]], self: Expr[RouteB[A]])
                            (using Quotes, Type[A], Type[B]): Expr[RouteB[B]] = {
    import quotes.reflect.*
    mirror match {

      case '{ $m: Mirror.ProductOf[B] { type MirroredElemTypes = EmptyTuple }} =>
        val ev = Expr.summonOrError[ValueOf[A]]
        '{ $self.const[B]($m.fromProduct(EmptyTuple))($ev) }

      case '{ $m: Mirror.ProductOf[B] { type MirroredElemTypes = t *: EmptyTuple }} =>
        Expr.summonOrError[A =:= t]
        '{
          $self.xmap[B](
            a => $m.fromProduct(a.asInstanceOf[t] *: EmptyTuple))(
            b => Tuple.fromProduct(b).asInstanceOf[A *: EmptyTuple].head)
        }

      case '{ type t <: Product; $m: Mirror.ProductOf[B] { type MirroredElemTypes = `t` }} =>
        Expr.summonOrError[A =:= t]
        '{
          $self.xmap[B](
            a => $m.fromProduct(a.asInstanceOf[t]))(
            b => Tuple.fromProduct(b).asInstanceOf[A])
        }
    }
  }

}
